/*******************************************************************************
this code is protected by the GNU affero GPLv3
author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
*******************************************************************************/
/*
we use libraries based on C posix runtime, then we need this to fetch
environment variables from the C posix runtime
*/
#include <stdlib.h>

#include <ulinux/compiler_types.h>
#include <ulinux/sysc.h>
#include <ulinux/types.h>
#include <ulinux/error.h>
#include <ulinux/fs.h>
#include <ulinux/signal/signal.h>
#include <ulinux/utils/mem.h>
#include <ulinux/utils/ascii/string/string.h>

#define INIT_C
#include "out.h"
#include "ulinux_namespace.h"
#include "modules.h"
#include "uevents.h"
#include "ramfs.h"
#include "globals.h"
#undef INIT_C

static void sigs_setup(void)
{
  ul mask=(~0);
  l r;

  OUT(PRE "setting up signals...\n");
  r=rt_sigprocmask(SIG_BLOCK,&mask,0,sizeof(mask));
  if(ISERR(r)){
    OUT("ERROR:unable to block all signals (except KILL and STOP)\n");
    exit_group(-1);
  }
  OUT("done\n");
}

static void sysfs_mount(void)
{
  OUT(PRE "mounting sysfs...\n");
  l r=mount(0,"/sys","sysfs",MS_NOATIME|MS_NODIRATIME,0);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to mount sysfs\n",r);
    exit_group(-1);
  }
}

static void sysfs_umount(void)
{
  OUT(PRE "umounting sysfs...\n");
  l r=umount("/sys");
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to umount sysfs\n",r);
    exit_group(-1);
  }
}

static void proc_mount(void)
{
  OUT(PRE "mounting proc...\n");
  l r=mount(0,"/proc","proc",MS_NOATIME|MS_NODIRATIME,0);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to mount proc\n",r);
    exit_group(-1);
  }
}

static void proc_umount(void)
{
  OUT(PRE "umounting proc...\n");
  l r=umount("/proc");
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to umount proc\n",r);
    exit_group(-1);
  }
}

static void devtmpfs_mount(void)
{
  OUT(PRE "mounting devtmpfs...\n");
  l r=mount(0,"/dev","devtmpfs",MS_NOATIME|MS_NODIRATIME,0);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to mount devtmpfs\n",r);
    exit_group(-1);
  }
}

static void devtmpfs_umount(void)
{
  OUT(PRE "umounting devtmpfs...\n");
  l r=umount("/dev");
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to umount devtmpfs\n",r);
    exit_group(-1);
  }
}

static void newroot_mount(void)
{
  OUT(PRE "mounting root device %s on /newroot, root file system is %s...\n",
                                                    root_dev_path,root_fs_type);
  l r=mount(root_dev_path,"/newroot",root_fs_type,MS_NOATIME|MS_NODIRATIME,0);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to mount root\n",r);
    exit_group(-1);
  }
}

static void newroot_chdir()
{
  OUT(PRE "chdir to /newroot...");
  l r=chdir("/newroot");
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to chdir to /newroot\n",r);
    exit_group(-1);
  }
  OUT("done\n");
}

static void newroot_move(void)
{
  OUT(PRE "mount moving . (/newroot) to / ...");
  l r=mount(".","/",0,MS_MOVE,0);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to mount move . (/newroot) to /\n",r);
    exit_group(-1);
  }
  OUT("done\n");
}

static void newroot_chroot(void)
{
  OUT(PRE "chroot to . (/newroot)...");
  l r=chroot(".");
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to chroot to . (/newroot)\n",r);
    exit_group(-1);
  }
  OUT("done\n");
}

/*this parameter *must* be around*/
#define PARAM_NAME "cinitramfs_root_uuid"
static void root_uuid_get(i argc, c **argv)
{
  u8 arg;

  /*we should use libc getenv to be friendly with the C runtime*/
  root_uuid=(u8*)getenv(PARAM_NAME);
  if(root_uuid) return;

  /*it's not in the environment, then could be in argv, see
    Documentation/kernel-parameters.txt*/

  if(argc==1) goto fatal_err;

  arg=1;
  loop{
    if(strncmp(argv[arg],PARAM_NAME,sizeof(PARAM_NAME)-1)==0) break;
    if(++arg==argc) goto fatal_err;
  }
  root_uuid=(u8*)argv[arg];
  loop if(*root_uuid++==(u8)'=') break;/*lookup for the key/value separator*/
  return;

fatal_err:
  OUT(PRE "ERROR:unable to find " PARAM_NAME "\n");
  exit_group(-1);
}
#undef PARAM_NAME

static void command_line_parse(i argc, c **argv)
{
  root_uuid_get(argc, argv);
}

/*
fds 0,1 and 2 are rw /dev/console, see linux/init/main.
Since we use standard C libs, be nice with the C runtime and use main instead of
_start entry point.
*/
i main(i argc,c **argv)
{
  u8 r0;
  l r1;
#ifndef QUIET 
  static u8 dprintf_buf[DPRINTF_BUF_SZ];
  g_dprintf_buf=dprintf_buf;
#endif
  sigs_setup();
  command_line_parse(argc,argv);
  uevents_setup();
  sysfs_mount();
  proc_mount();
  devtmpfs_mount();
  modules_setup();
  modules_probe_static();
  modules_probe_drivers();
  r0=uevents_process();
  if(r0==ROOT_NOT_FOUND){
    OUT(PRE "terminating cinitramfs since no proper root was found\n");
    exit_group(-1);
  }
  /*from here, we located our root*/
  uevents_cleanup();
  modules_probe_name(root_fs_type);
  modules_cleanup();
  newroot_mount();
  devtmpfs_umount();
  proc_umount();
  sysfs_umount();
  ramfs_cleanup();

  newroot_chdir();
  newroot_move();
  newroot_chroot();
  
  r1=execve("/bin/init");
  if(ISERR(r1)){OUT("ERROR(%ld):unable to execve /bin/init\n",r1);}
  exit_group(-1);
  /*unreachable*/
}
